# encoding: utf-8
"""
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    created by lane.chang on '15/04/2024'
    comment: 底层类
"""
import json
from datetime import datetime
from decimal import Decimal
from typing import Any, Union
from enum import Enum
from pydantic import BaseConfig, Extra
from laner.pydantic import BaseModel as PyBaseModel

import sqlalchemy.sql.schema
import sqlalchemy.types as types_
from sqlalchemy import MetaData, Integer, DateTime, Date, Time, DECIMAL, Boolean
from sqlalchemy.dialects.mysql import VARCHAR
from sqlalchemy.ext.declarative import as_declarative
from sqlalchemy import Column as _Column
from sqlalchemy import select

from project.core.function import serializer, is_subclass
from project.core.exception import NoRecordError


metadata = MetaData()


@as_declarative(metadata=metadata)
class BaseModel(object):
    # 修改剔除的列
    __edit_exclude_fields__ = ()
    # 扩展字段
    __edit_extra_fields__ = ()

    @classmethod
    def _get_columns(cls) -> list:
        """ 得到所有列
        :return:
        """
        return list(cls.__table__.columns.keys())

    @classmethod
    def field_exists(cls, filed_name) -> bool:
        """ 判断某列是否存在
        """
        return filed_name in cls._get_columns()

    def dict(self, *args, reverse: bool = False, exclude_undisplayed=True, filter_none=False) -> dict:
        """ model转换成dict(按照orm字段映射)
        :param args:
        :param reverse: 默认为False。 若为True, 则实现将args指定的字段剔除后对model进行dict化
        :param exclude_undisplayed: 过滤掉不显示字段
        :param filter_none: 是否过滤none值
        :return:
        """
        columns = set()
        _columns = self._get_columns()
        for v in dir(self):
            if v.startswith('_'):
                continue

            try:
                if not hasattr(self, v):
                    continue
            except:
                continue

            # 过滤可执行对象
            if callable(getattr(self, v)):
                continue

            if isinstance(getattr(self, v), (sqlalchemy.sql.schema.MetaData, sqlalchemy.orm.decl_api.registry,)):
                continue

            if args and v not in args and not reverse:
                continue

            if args and v in args and reverse:
                continue

            if exclude_undisplayed and v in _columns and not self.__class__.__table__.columns[v].is_displayed:
                continue

            # 过滤none值(如果是对象结构[list, dict], 则判断是否有值)
            _v = getattr(self, v)
            if filter_none and (_v is None or (isinstance(_v, (list, dict)) and not _v)):
                continue

            columns.add(v)

        model_dict = {column: getattr(self, column) for column in columns}
        return model_dict

    def set_model_value(self,  **kwargs):
        """设置model 的值(按照orm字段映射)
        :return:
        """
        for k, v in kwargs.items():
            if not hasattr(self.__class__, k):
                continue
            column = getattr(self.__class__, k)
            # 做好几种特殊的处理
            if isinstance(column, property):
                # 如果不是列, 而是属性, 这种情况是自定义额外的column, 比如密码, 就直接赋值
                try:
                    setattr(self, k, v)
                except:
                    pass
                continue
            if column.primary_key and (v == 0 or v == ''):
                # 如果是主键, 并且设置为0 或者为空
                setattr(self, k, v)
                continue
            if v == '' and column.nullable:
                # 如果为空, 并且该字段是可为null的, 则改为None
                setattr(self, k, None)
                continue
            if v is None and column.nullable:
                # 如果为null, 并且该字段是可为null的, 则改为None
                setattr(self, k, None)
                continue
            # 值不为空, 需要做一些类型转换
            if isinstance(column.type, Integer):
                # 数值型, 需要考虑为空的情况, 还需要考虑该字段允许不允许为空
                v = int(v) if v else 0
            elif isinstance(column.type, DECIMAL):
                # Decimal 类型, 需要考虑为空的情况, 还需要考虑该字段允许不允许为空
                v = Decimal(v) if v else 0
            elif isinstance(column.type, DateTime):
                # 日期时间(DT) 2016-1-1 9:18:10
                if isinstance(v, str):
                    v = datetime.strptime(v, '%Y-%m-%d %H:%M:%S')
                elif isinstance(v, Integer):
                    v = datetime.fromtimestamp(v)
            elif isinstance(column.type, Date):
                # 日期 2016-1-1
                if isinstance(v, str):
                    v = datetime.strptime(v, "%Y-%m-%d").date()
            elif isinstance(column.type, Time):
                # 时间: 9:18:10
                if isinstance(v, str):
                    sl = v.split(':')
                    if len(sl) == 3:
                        v = datetime.strptime(v, "%H:%M:%S").time()
                    elif len(sl) == 2:
                        v = datetime.strptime(v, "%H:%M").time()
                    elif len(sl) == 1:
                        v = datetime.strptime(v, "%H").time()
            elif isinstance(column.type, Boolean):
                # 布尔
                if not v:
                    v = False
                elif isinstance(v, str):
                    v = True if v.lower() in ('true', 't') else False
                elif isinstance(v, int):
                    v = True if v else False
                else:
                    v = False

            # 支持枚举类型
            if isinstance(v, Enum):
                self.__setattr__(k, v.value)
                continue

            self.__setattr__(k, v)

        return self


class Select:

    def __init__(self, selects, wheres=None, joins=None, orders=None, offset=None, limit=None, groups=None, left_joins=None):
        """
        """
        self.selects = selects
        self.wheres = wheres
        self.joins = joins
        self.left_joins = left_joins
        self.orders = orders
        self.offset = offset
        self.limit = limit
        self.groups = groups

    def _select(self):
        """ 组装sql
        :return:
        """
        query = select(self.selects)

        if self.joins is not None:
            if not isinstance(self.joins, (tuple, list)):
                query = query.join(*self.joins)
            else:
                for join in self.joins:
                    query = query.join(*join)

        if self.left_joins is not None:
            if not isinstance(self.joins, (tuple, list)):
                query = query.outerjoin(*self.left_joins)
            else:
                for left_join in self.left_joins:
                    query = query.outerjoin(*left_join)

        if self.wheres is not None:
            if not isinstance(self.wheres, (tuple, list)):
                query = query.where(self.wheres)
            else:
                query = query.where(*self.wheres)

        if self.orders is not None:
            if not isinstance(self.orders, (tuple, list)):
                query = query.order_by(self.orders)
            else:
                query = query.order_by(*self.orders)

        if self.offset is not None:
            query = query.offset(self.offset)

        if self.limit is not None:
            query = query.limit(self.limit)

        if self.groups is not None:
            if not isinstance(self.groups, (tuple, list)):
                query = query.group_by(self.groups)
            else:
                query = query.group_by(*self.groups)

        return query

    def _specified_field(self):
        """ 是否包含指定列检索
        :return:
        """
        specified_field = False
        if not isinstance(self.selects, (tuple, list)):
            specified_field = not is_subclass(self.selects, BaseModel)
        else:
            for m in self.selects:
                if not issubclass(type(m), BaseModel):
                    specified_field = True
                    break

        return specified_field

    async def _fetch(self, session):
        """
        :param session:
        :return:
        """
        result = await session.execute(self._select())

        return result

    async def fetch_one(self, session):
        """
        :return:
        """
        result = await self._fetch(session)

        if self._specified_field():
            return result.first()
        else:
            return result.scalars().first()

    async def fetch_all(self, session):
        """
        :param session:
        :return:
        """
        result = await self._fetch(session)

        if self._specified_field():
            return result
        else:
            return result.scalars().all()

    def subquery(self):
        """
        :return:
        """
        return self._select().subquery()

    def update(self, synchronize_session=False, **kwargs):
        """ 修改
        :param synchronize_session:
        :param kwargs:
        :return:
        """
        self.update(synchronize_session=synchronize_session, **kwargs)

    def delete(self):
        """ 删除
        :return:
        """
        self.delete()


class CustomModel(BaseModel):
    """ model - 表操作维度
    """
    __abstract__ = True

    @classmethod
    async def fetch_one(cls, session, selects=None, wheres=None, joins=None, orders=None, left_joins=None):
        """ 查询一条数据
        :param session:
        :param selects:
        :param wheres:
        :param joins:
        :param orders:
        :param left_joins:
        :return:
        """
        if selects is None:
            selects = cls
        record = await Select(selects=selects, wheres=wheres, joins=joins, orders=orders, left_joins=left_joins).fetch_one(session)

        return record

    @classmethod
    async def fetch_all(cls, session, selects=None, wheres=None, joins=None, orders=None, offset=None, limit=None, left_joins=None):
        """ 查询多条数据(支持排序)
        :param session:
        :param selects:
        :param wheres:
        :param joins:
        :param orders:
        :param offset:
        :param limit:
        :param left_joins:
        :return:
        """
        selects = selects or cls
        data = await Select(selects=selects, wheres=wheres, joins=joins, orders=orders, offset=offset, limit=limit, left_joins=left_joins).fetch_all(session)

        return data

    @classmethod
    async def create_modify(cls, session, wheres=None, commit=True, no_record_error=None, no_to_create=True, flush=True, **kwargs):
        """ 新增或修改
        :param session:
        :param commit: 是否提交
        :param wheres:
        :param no_record_error: 重复是否报错
        :param no_to_create: 没有就新增
        :param flush:
        :return:
        """
        record = None
        if wheres is not None:
            record = await cls.fetch_one(session, wheres=wheres)

        if not record:
            if no_record_error is not None:
                if no_record_error:
                    raise NoRecordError
            if not no_to_create:
                return
            record = cls()
            session.add(record)

        record.set_model_value(**kwargs)
        if commit:
            await session.commit()
        else:
            if flush:
                await session.flush()

        return record


# ==============用户自定义类型==============
class CustomList(types_.TypeDecorator):
    """ list 存储成 unicode
    """
    impl = types_.Unicode
    cache_ok = True

    def process_literal_param(self, value, dialect):
        pass

    @property
    def python_type(self):
        return ''

    def process_bind_param(self, value, dialect):
        """ set 时候调用
        :param value:
        :param dialect:
        :return:
        """
        # 这里只能处理None 和 empty, 否则会报错
        if value is None or value == '':
            return value
        if isinstance(value, str):
            return value
        elif isinstance(value, list):
            s = json.dumps(value, ensure_ascii=False)
            return s
        else:
            raise ValueError('the value type is error')

    def process_result_value(self, value, dialect):
        """ get 时候调用
        :param value:
        :param dialect:
        :return:
        """
        # 如果数据异常, 则吞掉这个错误
        value_dict = []
        try:
            value_dict = json.loads(value)
        except ValueError:
            pass
        finally:
            return value_dict


class CustomDict(types_.TypeDecorator):
    """ dict 存储成 unicode
    """

    impl = types_.VARCHAR
    cache_ok = True

    def process_literal_param(self, value, dialect):
        pass

    @property
    def python_type(self):
        return ''

    def process_bind_param(self, value, dialect):
        """ set 时候调用
        :param value:
        :param dialect:
        :return:
        """
        # 这里只能处理None 和 empty, 否则会报错
        if value is None or value == '':
            return value
        if isinstance(value, str):
            return value
        elif isinstance(value, dict):
            # json dumps 的结果是 str, 需要decode成unicode
            return json.dumps(serializer(value), ensure_ascii=False)
        else:
            raise ValueError('the value type is error')

    def process_result_value(self, value, dialect):
        """ get 时候调用
        :param value:
        :param dialect:
        :return:
        """
        # 如果数据异常, 则吞掉这个错误
        value_dict = {}
        try:
            value_dict = json.loads(value)
        except ValueError:
            pass
        finally:
            return value_dict


class Column(_Column):
    """ 扩展sqlalchemy-Column
    """
    def __init__(self, *args: Any, **kwargs: Any):
        """
        :rtype: object
        :param args:
        :param kwargs:
        """
        super(Column, self).__init__(*args, **kwargs)
        # 是否展示此属性
        self.is_displayed = kwargs.pop('is_displayed', True)

    def params(self, *optionaldict, **kwargs):
        pass

    def unique_params(self, *optionaldict, **kwargs):
        pass


class Element:
    """
    """
    def __init__(self, code, value, **kwargs):
        """
        :param code:
        :param value:
        """
        self.code = code
        self.value = value
        for k, v in kwargs.items():
            setattr(self, k, v)

    def __str__(self):
        """
        :return:
        """
        return self.code

    def dict(self):
        """
        :return:
        """
        return self.__dict__


class UEnum(Enum):

    @classmethod
    def members(cls):
        """
        :return:
        """
        return cls.__members__

    @classmethod
    def dict(cls):
        """
        :return:
        """
        return {v.name: v.value for v in cls}

    @classmethod
    def list(cls):
        """
        :return:
        """
        ret = []
        for key, ele in cls.members().items():
            model = ele.value
            if isinstance(model, Element):
                ret.append(model.dict())
                # ret.append({'code': model.code, 'value': model.value})
            else:
                ret.append(ele.value)

        return ret

    @classmethod
    def get_value(cls, code):
        """
        :param code:
        :return:
        """
        ret = ''
        for key, ele in cls.members().items():
            model = ele.value
            if isinstance(model, Element):
                if model.code == code:
                    ret = model.value
                    break
            else:
                if key == code:
                    ret = ele.value

        return ret

    @classmethod
    def enums(cls, fixed='', with_blank=False):
        """
        :return:
        """
        attrs = {k: str(v) for k, v in cls.dict().items()}
        if with_blank:
            attrs.update({'blank': ''})

        return Enum(f'{cls.__name__}Enum{fixed}', attrs)

    @classmethod
    def get_element(cls, code):
        """
        :param code:
        :return:
        """
        for key, ele in cls.members().items():
            model = ele.value
            if not isinstance(model, Element):
                return
            if model.code == code:
                return model

    @classmethod
    def get_element_value(cls, code, field_name='', default=''):
        """
        :param code:
        :param field_name:
        :param default:
        :return:
        """
        if not field_name:
            field_name = 'value'
        ele = cls.get_element(code)
        if not ele:
            return default

        if not hasattr(ele, field_name):
            return default

        return getattr(ele, field_name, default)

    @classmethod
    def codes(cls):
        """
        :return:
        """
        codes = []
        for key, ele in cls.members().items():
            model = ele.value
            if not isinstance(model, Element):
                codes.append(model.code)
            else:
                codes.append(key)

        return codes


class BodyParams(PyBaseModel):
    """ 承接接口参数
    """
    BaseConfig.extra = Extra.allow

    def pop(self, attr, default=None):
        """
        :return:
        """
        ret = default
        try:
            if hasattr(self, attr):
                ret = getattr(self, attr)
                delattr(self, attr)
        finally:
            return ret

    def exists(self, key):
        """
        :param key:
        :return:
        """
        return hasattr(self, key)
